package com.gitee.wsl.common.draw.gesture.computator


import android.graphics.PointF
import android.graphics.RectF
import com.gitee.wsl.common.chart.api.ViewportChangeListenerContain
import com.gitee.wsl.common.draw.gesture.ZoomType
import com.gitee.wsl.common.draw.area.listener.ViewportChangeListener
import com.gitee.wsl.common.draw.area.Viewport
import com.gitee.wsl.common.draw.area.animation.ChartViewportAnimator
import com.gitee.wsl.common.draw.area.animation.ChartViewportAnimatorImp
import com.gitee.wsl.common.ui.base.Padding
import com.gitee.wsl.android.ui.base.innerRect
import timber.log.Timber
import java.lang.Float.max
import java.lang.Float.min

/**
 * Computes raw points coordinates(in pixels), holds content area dimensions and chart viewport.
 */
open class ChartCommutator() : ViewportChangeListenerContain<ViewportChangeListener> {

    var isViewportCalculationEnabled: Boolean = false

    /**
     * This rectangle represents the currently visible chart values ranges. The currently visible chart X values are
     * from this rectangle's left to its right. The currently visible chart Y values are from this rectangle's top to
     * its bottom.
     */
    @kotlin.jvm.JvmField
    var currentViewport = Viewport()

    /**
     * Returns maximum viewport - values ranges extremes.
     */
    @JvmField
    var maximumViewport = Viewport()

    var bodyViewport = Viewport()

    var minimumViewportWidth = 0f
        protected set
    var minimumViewportHeight = 0f
        protected set

    /**
     * Returns content rectangle in pixels.
     *
     * @see .setContentRect
     */
    //contentRectMinusAllMargins <= contentRectMinusAxesMargins <= maxContentRect
    val contentRectMinusAllMargins :RectF
       get() = bodyViewport.innerRect

    /**
     * Returns content rectangle with chart internal margins, for example for LineChart contentRectMinusAxesMargins is
     * bigger
     * than contentRectMinusAllMargins by point radius, thanks to that points are not cut on edges.
     *
     * @see .setContentRect
     */
    /*val contentRectMinusAxesMargins = RectF()

    val maxContentRect = RectF()*/


    val viewportAnimator: ChartViewportAnimator by lazy{
        ChartViewportAnimatorImp(this)
    }

    override val viewportChangeListenerList: MutableList<ViewportChangeListener> = mutableListOf()

    fun fireViewportChanged(){
        viewportChangeListenerList.forEach {
            it.onViewportChanged(currentViewport)
        }
    }

    var isZoomEnabled = true
    var isScrollEnabled = true
    var isValueTouchEnabled = true
    var isValueSelectionEnabled = false

    var isContainerScrollEnabled = false

    ///////////////////////////////////////////////////////////////////
    //zoom
    var zoomType: ZoomType = ZoomType.HORIZONTAL_AND_VERTICAL

    var maxZoom = DEFAULT_MAXIMUM_ZOOM
        set(value) {
            var maxZoom = value
            if (maxZoom < 1) {
                maxZoom = 1f
            }
            field = maxZoom
            computeMinimumWidthAndHeight()
            setCurrentViewport(currentViewport)
        }

    val zoomLevel: Float
        get() {
            return (maximumViewport.width / currentViewport.width).coerceAtLeast(maximumViewport.height / currentViewport.height)
        }

    var scaleValue : Float = 1f

    var scaleValueX : Float = 1f

    var scaleValueY : Float = 1f

    /*fun setChartRange(width: Float, height: Float) {
        setChartRange(0f,0f,width, height)
    }*/

    fun setChartRange(startX: Float, startY: Float, width: Float, height: Float) {

        setMaxViewport(startX, startY, startX+width, startY+height)
        setBodyViewport(startX, startY, startX+width, startY+height)
        setCurrentViewport(startX, startY, startX+width, startY+height)
    }

    ////////////////////////////////////////////////////////////////////////////////
    //main

    /**
     * Calculates available width and height. Should be called when chart dimensions change. ContentRect is relative to
     * chart view not the device's screen.
     */
    /*fun setContentRect(
        width: Float, height: Float, paddingLeft: Float, paddingTop: Float, paddingRight: Float,
        paddingBottom: Float
    ) {
        chartWidth = width
        chartHeight = height
        maxContentRect[paddingLeft, paddingTop, width - paddingRight] = height - paddingBottom
        contentRectMinusAxesMargins.set(maxContentRect)
        contentRectMinusAllMargins.set(maxContentRect)
    }*/

    fun resetContentRect() {
        /*contentRectMinusAxesMargins.set(maxContentRect)
        contentRectMinusAllMargins.set(maxContentRect)*/
    }

    fun resetViewports() {
        maximumViewport.reset()
        bodyViewport.reset()
        currentViewport.reset()
        fireViewportChanged()
    }


    ////////////////////////////////////////////////////////////////////////////////////////
    //body
    /**
     * Returns viewport for visible part of chart, for most charts it is equal to current viewport.
     *
     * @return
     */
    open var visibleViewport: Viewport
        get() = currentViewport
        set(value) {
            setCurrentViewport(value)
        }

    fun insetContentRect(deltaLeft: Float=0f, deltaTop: Float=0f, deltaRight: Float=0f, deltaBottom: Float=0f) {
        /*contentRectMinusAxesMargins.left = contentRectMinusAxesMargins.left + deltaLeft
        contentRectMinusAxesMargins.top = contentRectMinusAxesMargins.top + deltaTop
        contentRectMinusAxesMargins.right = contentRectMinusAxesMargins.right - deltaRight
        contentRectMinusAxesMargins.bottom = contentRectMinusAxesMargins.bottom - deltaBottom
        insetContentRectByInternalMargins(deltaLeft, deltaTop, deltaRight, deltaBottom)*/
        bodyViewport.padding(deltaLeft, deltaTop, deltaRight, deltaBottom)
    }

    fun insetContentRect(padding: Padding) {
        insetContentRect(padding.left,padding.top,padding.right,padding.bottom)
    }

    /*fun insetContentRectByInternalMargins(
        deltaLeft: Int,
        deltaTop: Int,
        deltaRight: Int,
        deltaBottom: Int
    ) {
        contentRectMinusAllMargins.left = contentRectMinusAllMargins.left + deltaLeft
        contentRectMinusAllMargins.top = contentRectMinusAllMargins.top + deltaTop
        contentRectMinusAllMargins.right = contentRectMinusAllMargins.right - deltaRight
        contentRectMinusAllMargins.bottom = contentRectMinusAllMargins.bottom - deltaBottom
    }

    fun insetContentRectByInternalMargins(padding: Padding) {
        insetContentRectByInternalMargins(padding.left.toInt(),padding.top.toInt(),padding.right.toInt(),padding.bottom.toInt())
    }*/

    /**
     * Set current viewport to the same values as viewport passed in parameter. This method use deep copy so parameter
     * can be safely modified later. Current viewport must be equal or smaller than maximum viewport.
     *
     * @param viewport
     */
    fun setCurrentViewport(viewport: Viewport) {
        constrainViewport(viewport.left, viewport.top, viewport.right, viewport.bottom)
    }

    /**
     * Set new values for curent viewport, that will change what part of chart is visible. Current viewport must be
     * equal or smaller than maximum viewport.
     */
    fun setCurrentViewport(left: Float, top: Float, right: Float, bottom: Float) {
        constrainViewport(left, top, right, bottom)
    }

    /**
    * Checks if new viewport doesn't exceed max available viewport.
    */
    open fun constrainViewport(left1: Float, top1: Float, right1: Float, bottom1: Float) {
        var left = left1
        var top = top1
        var right = right1
        var bottom = bottom1
        if (right - left < minimumViewportWidth) {
            // Minimum width - constrain horizontal zoom!
            right = left + minimumViewportWidth
            if (left < maximumViewport.left) {
                left = maximumViewport.left
                right = left + minimumViewportWidth
            } else if (right > maximumViewport.right) {
                right = maximumViewport.right
                left = right - minimumViewportWidth
            }
        }
        if (bottom - top < minimumViewportHeight) {
            // Minimum height - constrain vertical zoom!
            bottom = top + minimumViewportHeight
            if (top < maximumViewport.top) {
                top = maximumViewport.top
                bottom = top + minimumViewportHeight
            } else if (bottom > maximumViewport.bottom) {
                bottom = maximumViewport.bottom
                top = bottom - minimumViewportHeight
            }
        }
        Timber.d("chage current viewport from :$currentViewport")
        currentViewport.left = max(maximumViewport.left,left)
        currentViewport.top = max(maximumViewport.top,top)
        currentViewport.right = min( maximumViewport.right,right)
        currentViewport.bottom = min(maximumViewport.bottom,bottom)
        Timber.d("chage current viewport to :$currentViewport")
        fireViewportChanged()
    }

    /**
     * Set maximum viewport to the same values as viewport passed in parameter. This method use deep copy so parameter
     * can be safely modified later.
     *
     * @param maxViewport
     */
    fun setMaxViewport(maxViewport: Viewport) {
        setMaxViewport(maxViewport.left, maxViewport.top, maxViewport.right, maxViewport.bottom)
    }

    /**
     * Set new values for maximum viewport, that will change what part of chart is visible.
     */
    fun setMaxViewport(left: Float, top: Float, right: Float, bottom: Float) {
        maximumViewport[left, top, right] = bottom
        Timber.d("chage current max viewport from :$maximumViewport")
        computeMinimumWidthAndHeight()
    }

    fun setBodyViewport(left: Float, top: Float, right: Float, bottom: Float) {
        bodyViewport[left, top, right] = bottom
        Timber.d("chage current body viewport from :$bodyViewport")
    }


    private fun computeMinimumWidthAndHeight() {
        minimumViewportWidth = maximumViewport.width / maxZoom
        minimumViewportHeight = maximumViewport.height / maxZoom
    }


    fun setCurrentViewportWithAnimation(targetViewport: Viewport) {
        viewportAnimator.cancelAnimation()
        viewportAnimator.startAnimation(currentViewport, targetViewport)
    }

    fun setCurrentViewportWithAnimation(targetViewport: Viewport, duration: Long) {
        viewportAnimator.cancelAnimation()
        viewportAnimator.startAnimation(currentViewport, targetViewport, duration)

    }

    /////////////////////////////////////////////////////////////////////////
    //check
    /**
     * Translates chart value into raw pixel value. Returned value is absolute pixel X coordinate. If this method
     * return
     * 0 that means left most pixel of the screen.
     */
    open fun computeRawX(valueX: Float): Float {
        // TODO: (contentRectMinusAllMargins.width() / currentViewport.width()) can be recalculated only when viewport
        // change.
        val pixelOffset = (valueX - currentViewport.left) * (contentRectMinusAllMargins.width() / currentViewport.width)
        return contentRectMinusAllMargins.left + pixelOffset
    }

    /**
     * Translates chart value into raw pixel value. Returned value is absolute pixel Y coordinate. If this method
     * return
     * 0 that means top most pixel of the screen.
     */
    open fun computeRawY(valueY: Float): Float {
        val pixelOffset = (currentViewport.bottom - valueY) * (contentRectMinusAllMargins.height() / currentViewport.height)
        return contentRectMinusAllMargins.bottom - pixelOffset
    }

    /**
     * Translates viewport distance int pixel distance for X coordinates.
     */
    fun computeRawDistanceX(distance: Float): Float {
        return distance * (contentRectMinusAllMargins.width() / currentViewport.width)
    }

    /**
     * Translates viewport distance int pixel distance for X coordinates.
     */
    fun computeRawDistanceY(distance: Float): Float {
        return distance * (contentRectMinusAllMargins.height() / currentViewport.height)
    }

    /**
     * Finds the chart point (i.e. within the chart's domain and range) represented by the given pixel coordinates, if
     * that pixel is within the chart region described by [.contentRectMinusAllMargins]. If the point is found,
     * the "dest"
     * argument is set to the point and this function returns true. Otherwise, this function returns false and
     * "dest" is
     * unchanged.
     */
    fun rawPixelsToDataPoint(x: Float, y: Float, dest: PointF): Boolean {
        if (contentRectMinusAllMargins.contains(x, y)) {
            val index= currentViewport.left + (x - contentRectMinusAllMargins.left) * currentViewport.width / contentRectMinusAllMargins.width()
            dest[index] = currentViewport.bottom + (y - contentRectMinusAllMargins.bottom) * currentViewport.height / -contentRectMinusAllMargins.height()
            return true
        }
        return false
    }

    /**
     * Computes the current scrollable surface size, in pixels. For example, if the entire chart area is visible, this
     * is simply the current size of [.contentRectMinusAllMargins]. If the chart is zoomed in 200% in both
     * directions, the
     * returned size will be twice as large horizontally and vertically.
     */
    fun computeScrollSurfaceSize(out: PointF) {
        out[maximumViewport.width * contentRectMinusAllMargins.width() / currentViewport.width] =
            maximumViewport.height * contentRectMinusAllMargins.height() / currentViewport.height
    }

    /**
     * Check if given coordinates lies inside contentRectMinusAllMargins.
     */
    fun isWithinContentRect(x: Float, y: Float, precision: Float): Boolean {
        if (x >= contentRectMinusAllMargins.left - precision && x <= contentRectMinusAllMargins.right + precision) {
            if (y <= contentRectMinusAllMargins.bottom + precision && y >= contentRectMinusAllMargins.top - precision) {
                return true
            }
        }
        return false
    }

    ///////////////////////////////////////////////////////////////////////
    //tran
    /**
     * Sets the current viewport (defined by [.currentViewport]) to the given X and Y positions.
     */
    fun setViewportTopLeft(left1: Float, top1: Float) {
        /**
         * Constrains within the scroll range. The scroll range is simply the viewport extremes (AXIS_X_MAX,
         * etc.) minus
         * the viewport size. For example, if the extrema were 0 and 10, and the viewport size was 2, the scroll range
         * would be 0 to 8.
         */
        var left = left1
        var top = top1
        val curWidth = currentViewport.width
        val curHeight = currentViewport.height
        left = max(maximumViewport.left,min(left,maximumViewport.right - curWidth))
        top = max(maximumViewport.top,min(top,maximumViewport.bottom - curHeight))
        Timber.d("setViewportTopLeft left:$left1,top:$top1")
        constrainViewport(left, top, left + curWidth, top + curHeight)
    }


    ////////////////////////////////////////////////////////////////////////
    //scroll
    fun moveTo(x: Float, y: Float) {
        currentViewport = computeScrollViewport(x, y)
        fireViewportChanged()
    }

    fun moveToWithAnimation(x: Float, y: Float) {
        val scrollViewport = computeScrollViewport(x, y)
        setCurrentViewportWithAnimation(scrollViewport)
    }

    private fun computeScrollViewport(x: Float, y: Float): Viewport {
        val maxViewport = maximumViewport
        val currentViewport = currentViewport
        val scrollViewport = Viewport(currentViewport)
        if (maxViewport.contains(x, y)) {
            val width = currentViewport.width
            val height = currentViewport.height
            val halfWidth = width / 2
            val halfHeight = height / 2
            var left = x - halfWidth
            var top = y - halfHeight
            left = max(maxViewport.left, min(left, maxViewport.right - width))
            top = max(maxViewport.top, min(top, maxViewport.bottom - height))
            scrollViewport[left, top, left + width] = top + height
        }
        return scrollViewport
    }

    /**
     * When embedded in a ViewPager, this will be called in order to know if we can scroll.
     * If this returns true, the ViewPager will ignore the drag so that we can scroll our content.
     * If this return false, the ViewPager will assume we won't be able to scroll and will consume the drag
     *
     * @param direction Amount of pixels being scrolled (x axis)
     * @return true if the chart can be scrolled (ie. zoomed and not against the edge of the chart)
     */
    fun canScrollHorizontally(direction: Int): Boolean {
        if (zoomLevel <= 1.0) {
            return false
        }
        return if (direction < 0) {
            currentViewport.left > maximumViewport.left
        } else {
            currentViewport.right < maximumViewport.right
        }
    }


    /////////////////////////////////////////////////////////////////
    //zoom
    fun compScaleValue(){
       /* when(zoomType){
            ZoomType.HORIZONTAL_AND_VERTICAL-> {
                scaleValueX = zoomLevel
                scaleValueY = zoomLevel
            }
            ZoomType.HORIZONTAL -> scaleValueX =   maximumViewport.width / currentViewport.width
            ZoomType.VERTICAL -> scaleValueY = maximumViewport.height / currentViewport.height
            else -> {}
        }*/
    }

    fun restRealPoint(x:Float, y:Float): PointF?{
        val point=PointF( x / scaleValueX,y / scaleValueY)
        val isInView = currentViewport.contains(point.x,point.y)
        Timber.d("restRealPoint zoom x:${scaleValueX},y:${scaleValueY},form $x,$y to $point is in View :$isInView ->${currentViewport} ")
        if(!isInView)  return null
        return point
    }

    fun setScaleFactor(focusX:Float,focusY:Float,scaleFactor:Float){
            scaleValue *= scaleFactor
            scaleValue = min( maxZoom,max(scaleValue,0.5f))

            when(zoomType){
                ZoomType.HORIZONTAL_AND_VERTICAL-> {
                   scaleValueX = scaleValue
                   scaleValueY = scaleValue
                }
                ZoomType.HORIZONTAL ->  scaleValueX =   scaleValue
                ZoomType.VERTICAL ->  scaleValueY = scaleValue
                else -> {}
            }
            setZoomLevel(focusX, focusY, scaleValueX, scaleValueY)
    }

    fun setZoomLevel(x: Float, y: Float, scaleValueX: Float,scaleValueY:Float) {
        val zoomViewport = computeZoomViewport(x, y, scaleValueX, scaleValueY)
        currentViewport = zoomViewport
        fireViewportChanged()
    }

    fun setZoomLevelWithAnimation(x: Float, y: Float, scaleValueX: Float,scaleValueY:Float) {
        val zoomViewport = computeZoomViewport(x, y, scaleValueX, scaleValueY)
        setCurrentViewportWithAnimation(zoomViewport)
    }


    private fun computeZoomViewport(x: Float, y: Float, scaleValueX: Float,scaleValueY:Float): Viewport {
        val maxViewport = maximumViewport
        val zoomViewport = Viewport(maximumViewport)
        if (maxViewport.contains(x, y)) {
            /*if (zoomLevel < 1) {
                zoomLevel = 1f
            } else if (zoomLevel > maxZoom) {
                zoomLevel = maxZoom
            }*/
            //val newWidth = zoomViewport.width / zoomLevel
            //val newHeight = zoomViewport.height / zoomLevel
            val newWidth = zoomViewport.width / scaleValueX
            val newHeight = zoomViewport.height / scaleValueY
            val halfWidth = newWidth / 2
            val halfHeight = newHeight / 2
            var left = x - halfWidth
            var right = x + halfWidth
            var top = y - halfHeight
            var bottom = y + halfHeight
            if (left < maxViewport.left) {
                left = maxViewport.left
                right = left + newWidth
            } else if (right > maxViewport.right) {
                right = maxViewport.right
                left = right - newWidth
            }
            if (top < maxViewport.top) {
                top = maxViewport.top
                bottom = top + newHeight
            } else if (bottom > maxViewport.bottom) {
                bottom = maxViewport.bottom
                top = bottom - newHeight
            }
            if (ZoomType.HORIZONTAL_AND_VERTICAL === zoomType) {
                zoomViewport[left, top, right] = bottom
            } else if (ZoomType.HORIZONTAL === zoomType) {
                zoomViewport.left = left
                zoomViewport.right = right
            } else if (ZoomType.VERTICAL === zoomType) {
                zoomViewport.top = top
                zoomViewport.bottom = bottom
            }
        }
        return zoomViewport
    }


    companion object {
        /**
         * Maximum chart zoom.
         */
        protected const val DEFAULT_MAXIMUM_ZOOM = 20f
    }
}